package com.mvc.core;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;

import org.bson.Document;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.aggregation.Aggregation;
import org.springframework.data.mongodb.core.aggregation.AggregationResults;
import org.springframework.data.mongodb.core.aggregation.MatchOperation;
import org.springframework.data.mongodb.core.aggregation.ProjectionOperation;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;

import com.mvc.domain.mongo.Entity;

/**
 * 默认db访问实现
 * 
 * @author lt
 *
 * @param <T>
 */
public abstract class BaseMongo<T extends Entity> {

	@Autowired
	protected MongoTemplate mongoTemplate;

	/**
	 * class 实例
	 */
	private Class<T> clazz;

	/**
	 * 钩子方法
	 */
	@SuppressWarnings("unchecked")
	protected synchronized Class<T> getEntityClass() {
		if (clazz != null) {
			return clazz;
		}
		Type clazzType = getClass().getGenericSuperclass();
		if (!(clazzType instanceof ParameterizedType)) {
			throw new UnsupportedOperationException("invalid repository.");
		}
		clazz = (Class<T>) ((ParameterizedType) clazzType).getActualTypeArguments()[0];
		return clazz;
	}

	/**
	 * 查询构建
	 * 
	 * @param t
	 * @return
	 */
	protected abstract Criteria buildCriteria(T t) throws Exception;

	/**
	 * 修改构建
	 * 
	 * @param t
	 * @return
	 */
	protected abstract Update buildUpdate(T t) throws Exception;

	/**
	 * 保存一个对象
	 * 
	 * @param t
	 */
	public void save(T t) {
		this.mongoTemplate.save(t);
	}

	/**
	 * 根据id获取
	 * 
	 * @param id
	 * @return
	 */
	public T findById(String id) {
		Query query = new Query();
		Criteria criteria = Criteria.where("id").is(id);
		query.addCriteria(criteria);
		return (T) this.mongoTemplate.findOne(query, getEntityClass());
	}

	/**
	 * 根据条件查询单个实体
	 * 
	 * @param criteria 查询条件
	 * @return
	 */
	public T findByCriteria(Criteria criteria) {
		Query query = new Query(criteria);
		return (T) this.mongoTemplate.findOne(query, getEntityClass());
	}

	/**
	 * 根据id删除
	 * 
	 * @param id
	 */
	public void deleteById(String id) {
		Criteria criteria = Criteria.where("_id").in(id);
		this.mongoTemplate.findAndRemove(new Query(criteria), getEntityClass());
	}

	/**
	 * 删除对象
	 * 
	 * @param t
	 */
	public void delete(T t) {
		this.mongoTemplate.remove(t);
	}

	/**
	 * 删除对象
	 * 
	 * @param t
	 */
	public void delete(Criteria criteria) {
		Query query = new Query(criteria);
		this.mongoTemplate.remove(query, getEntityClass());
	}

	/**
	 * 修改
	 * 
	 * @param query
	 * @param update
	 */
	public void update(Criteria criteria, Update update) {
		Query query = new Query(criteria);
		this.mongoTemplate.updateMulti(query, update, getEntityClass());
	}

	/**
	 * 修改
	 * 
	 * @param id     主键
	 * @param query
	 * @param update
	 */
	public void update(String id, T t) throws Exception {
		Query query = new Query(Criteria.where("_id").is(id));
		this.mongoTemplate.updateMulti(query, buildUpdate(t), getEntityClass());
	}

	/**
	 * 原子操作修改
	 * 
	 * @param query
	 * @param update
	 * @return
	 */
	public T findAndModify(String id, T t) throws Exception {
		Query query = new Query(Criteria.where("_id").is(id));
		mongoTemplate.findAndModify(query, buildUpdate(t), getEntityClass());
		return mongoTemplate.findOne(query, getEntityClass());
	}

	/**
	 * 原子操作修改
	 * 
	 * @param query
	 * @param update
	 * @return
	 */
	public T findAndModify(Criteria criteria, Update update) {
		Query query = new Query(criteria);
		mongoTemplate.findAndModify(query, update, getEntityClass());
		return mongoTemplate.findOne(query, getEntityClass());
	}

	/**
	 * 列表查询
	 * 
	 * @param t 参数
	 * @return
	 */
	public List<T> list(T t) throws Exception {
		return list(t, null, null, null);
	}

	/**
	 * 列表查询
	 * 
	 * @param t    参数
	 * @param page 分页
	 * @return
	 */
	public List<T> list(T t, Pageable page) throws Exception {
		return list(t, page, null, null);
	}

	/**
	 * 列表查询
	 * 
	 * @param t     参数
	 * @param page  分页
	 * @param field 字段
	 * @return
	 * @throws Exception
	 */
	public List<T> list(T t, Pageable page, String[] field) throws Exception {
		return list(t, page, field, null);
	}

	/**
	 * 列表查询排序
	 * 
	 * @param t     参数
	 * @param page  分页
	 * @param field 字段
	 * @param sort  排序
	 * @return
	 */
	public List<T> list(T t, Pageable page, String[] field, Sort sort) throws Exception {
		// 1.查询字段设置
		Query query = new Query();
		if (field != null && field.length > 0) {
			for (String string : field) {
				query.fields().include(string);
			}
		}
		// 2.是否要分页
		if (page != null) {
			query.with(page);
		}
		// 3.是否排序
		if (sort != null) {
			query.with(sort);
		}
		Criteria buildCriteria = buildCriteria(t);
		if (buildCriteria != null) {
			query.addCriteria(buildCriteria);
		}
		return mongoTemplate.find(query, getEntityClass());
	}

	/**
	 * 自定义列表查询排序
	 * 
	 * @param t     参数
	 * @param field 字段
	 * @param sort  排序
	 * @return
	 */
	public List<T> listByCriteria(Criteria criteria, Pageable page, String[] field, Sort sort) {
		// 1.查询字段设置
		Query query = new Query();
		if (field != null && field.length > 0) {
			for (String string : field) {
				query.fields().include(string);
			}
		}
		// 2.是否要分页
		if (page != null) {
			query.with(page);
		}
		// 3.是否排序
		if (sort != null) {
			query.with(sort);
		}
		query.addCriteria(criteria);
		return mongoTemplate.find(query, getEntityClass());
	}

	/**
	 * 自定义分页，列表查询排序
	 * 
	 * @param t     参数
	 * @param field 字段
	 * @param sort  排序
	 * @return
	 */
	public List<T> listByQuery(Query query) {
		return mongoTemplate.find(query, getEntityClass());
	}

	/**
	 * 统计
	 * 
	 * @param t
	 * @return
	 */
	public long count(T t) throws Exception {
		return mongoTemplate.count(Query.query(buildCriteria(t)), getEntityClass());
	}

	/**
	 * 分组，统计个数
	 * 
	 * @param criteria     分组查询条件
	 * @param groupByField 分组字段
	 * @return
	 */
	public List<Document> groupByCount(Criteria criteria, String... groupByField) {
		MatchOperation match = Aggregation.match(criteria);
		Aggregation group = null;
		if (groupByField.length == 1) {
			ProjectionOperation project = Aggregation.project("count").and(groupByField[0]).previousOperation();
			group = Aggregation.newAggregation(match, Aggregation.group(groupByField).count().as("count"), project);
		} else {
			group = Aggregation.newAggregation(match, Aggregation.group(groupByField).count().as("count"));
		}

		AggregationResults<Document> result = mongoTemplate.aggregate(group, this.getEntityClass(), Document.class);
		return result.getMappedResults();
	}

	/**
	 * 分组，汇总
	 * 
	 * @param criteria     分组查询条件
	 * @param sumField     sum字段
	 * @param groupByField 分组字段
	 * @return
	 */
	public List<Document> groupBySum(Criteria criteria, String sumField, String... groupByField) {
		MatchOperation match = Aggregation.match(criteria);
		Aggregation group = null;
		if (groupByField.length == 1) {
			ProjectionOperation project = Aggregation.project("sum").and(groupByField[0]).previousOperation();
			group = Aggregation.newAggregation(match, Aggregation.group(groupByField).sum(sumField).as("sum"), project);
		} else {
			group = Aggregation.newAggregation(match, Aggregation.group(groupByField).sum(sumField).as("sum"));
		}

		AggregationResults<Document> result = mongoTemplate.aggregate(group, this.getEntityClass(), Document.class);
		return result.getMappedResults();
	}

	/**
	 * 分组，平均
	 * 
	 * @param criteria     分组查询条件
	 * @param sumField     聚合字段
	 * @param groupByField 分组字段
	 * @return
	 */
	public List<Document> groupByAvg(Criteria criteria, String sumField, String[] groupByField) {
		MatchOperation match = Aggregation.match(criteria);
		Aggregation group = null;
		if (groupByField.length == 1) {
			ProjectionOperation project = Aggregation.project("avg").and(groupByField[0]).previousOperation();
			group = Aggregation.newAggregation(match, Aggregation.group(groupByField).avg(sumField).as("avg"), project);
		} else {
			group = Aggregation.newAggregation(match, Aggregation.group(groupByField).avg(sumField).as("avg"));
		}

		AggregationResults<Document> result = mongoTemplate.aggregate(group, this.getEntityClass(), Document.class);
		return result.getMappedResults();
	}

	/**
	 * 分组，最大
	 * 
	 * @param criteria     分组查询条件
	 * @param sumField     聚合字段
	 * @param groupByField 分组字段
	 * @return
	 */
	public List<Document> groupByMax(Criteria criteria, String sumField, String... groupByField) {
		MatchOperation match = Aggregation.match(criteria);
		Aggregation group = null;
		if (groupByField.length == 1) {
			ProjectionOperation project = Aggregation.project("max").and(groupByField[0]).previousOperation();
			group = Aggregation.newAggregation(match, Aggregation.group(groupByField).max(sumField).as("max"), project);
		} else {
			group = Aggregation.newAggregation(match, Aggregation.group(groupByField).max(sumField).as("max"));
		}

		AggregationResults<Document> result = mongoTemplate.aggregate(group, this.getEntityClass(), Document.class);
		return result.getMappedResults();
	}

	/**
	 * 分组，最小
	 * 
	 * @param criteria     分组查询条件
	 * @param sumField     聚合字段
	 * @param groupByField 分组字段
	 * @return
	 */
	public List<Document> groupByMin(Criteria criteria, String sumField, String... groupByField) {
		MatchOperation match = Aggregation.match(criteria);
		Aggregation group = null;
		if (groupByField.length == 1) {
			ProjectionOperation project = Aggregation.project("min").and(groupByField[0]).previousOperation();
			group = Aggregation.newAggregation(match, Aggregation.group(groupByField).min(sumField).as("min"), project);
		} else {
			group = Aggregation.newAggregation(match, Aggregation.group(groupByField).min(sumField).as("min"));
		}
		AggregationResults<Document> result = mongoTemplate.aggregate(group, this.getEntityClass(), Document.class);
		return result.getMappedResults();
	}

}
